package com.globo.opensocial;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * Contains information about a OpenSocial Application.
 * 
 * @author falcao
 * 
 */
public class Application {

	private String id;
	private String title;
	private String description;
	private String thumbnail;
	private String host;
	private String output;
	private String charset;
	private String outputTest;

	private int height;

	private boolean debug = false;
	private boolean singleton = false;

	/* main app source and code */
	private JavaScript code;
	private File source;

	private List<String> js;
	private List<String> require;
	private List<String> css;
	private List<String> include;
	private List<String> templates;

	static private Application instance;

	/**
	 * Returns the single instance
	 * 
	 * @return
	 */
	static public Application getInstance() {
		return instance;
	}

	static public void clearInstance() {
		instance = null;
	}

	public Application() throws Exception {
		if (instance != null) {
			throw new Exception("Application already exists");
		}
		instance = this;
		require = new ArrayList<String>();
		css = new ArrayList<String>();
		include = new ArrayList<String>();
		templates = new ArrayList<String>();
		js = new ArrayList<String>();
	}

	/**
	 * Add a css file to be included in application
	 * 
	 * @param fileName
	 */
	public void addCss(String fileName) {
		css.add(fileName);
	}

	public void addInclude(String value) {
		include.add(value);
	}

	/**
	 * Adds a js file to be include in the application.
	 * 
	 * @param fileName
	 */
	public void addJavaScript(String fileName) {

		js.add(fileName);
	}

	public List<String> getJavaScripts() {
		return js;
	}

	/**
	 * Adds a template path
	 * 
	 * @param fileName
	 */
	public void addTemplate(String templatePath) {
		templates.add(templatePath);
	}

	/**
	 * Set the output file.
	 * 
	 * @param fileName
	 */
	public void setOutput(String fileName) {
		output = fileName;
	}

	/**
	 * Add a required package to application.
	 * 
	 * @param required
	 */
	public void addRequire(String required) {
		require.add(required);
	}

	public void build() throws FileNotFoundException {
		if (!source.isFile()) {
			System.err.println("[ERRO] Main code?");
		}
		code = new JavaScript(this, source);
	}

	public File getAbsolutePath(String fileName) {
		return new File(source.getParentFile(), fileName);
	}

	public String getChaset() {
		return charset;
	}

	public List<String> getCss() {
		return css;
	}

	public String getDescription() {
		return description;
	}

	public int getHeight() {
		return height;
	}

	public String getHost() {
		if (host.endsWith("/")) {
			return host;
		} else {
			return host + "/";
		}
	}

	public String getId() {
		return id;
	}

	/**
	 * Returns image path with
	 * 
	 * @param imageName
	 * @return
	 */
	public String getImagePath() {
		return getHost() + "resources/";
	}

	public List<String> getInclude() {
		return include;

	}

	public List<String> getRequire() {
		return require;
	}

	public String getThumbnail() {
		return thumbnail;
	}

	public String getTitle() {

		return title;
	}

	public boolean hasCss() {
		return (css.size() > 0);

	}

	public boolean isDebug() {
		return debug;
	}

	public boolean isSingleton() {
		return singleton;
	}

	public void setCharset(String charset) {
		this.charset = charset;
	}

	/**
	 * Set the main javascript code.
	 * 
	 * @param file
	 */
	public void setCode(File file) {
		source = file;
	}

	/**
	 * Set debug
	 * 
	 * @param debug
	 */
	public void setDebug(boolean debug) {
		this.debug = debug;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public void setHeight(int height) {
		this.height = height;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public void setId(String id) {
		this.id = id;
	}

	public void setSingleton(boolean singleton) {
		this.singleton = singleton;
	}

	public void setThumbnail(String thumbnail) {
		this.thumbnail = thumbnail;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	/**
	 * Write to default path.
	 * 
	 * @throws IOException
	 */
	public void write() throws IOException {

		File output = null;

		if (this.output == null) {
			this.output = "app.xml";
			output = new File(source.getParentFile(), this.output);
		} else {
			output = new File(this.output);
			if (output.isDirectory()) {
				String source = this.source.getName();
				String name = source.substring(0, source.indexOf("."));
				output = new File(this.output, name + ".xml");
			}
		}

		Writer out = new OutputStreamWriter(new FileOutputStream(output),
				charset);

		write(out);

		out.flush();
	}

	/**
	 * Write to default path.
	 * 
	 * @throws IOException
	 */
	public void writeTests() throws IOException {

		if (this.outputTest == null) {
			this.outputTest = "tests.htm";
		}

		File output = new File(source.getParentFile(), this.outputTest);

		Writer out = new OutputStreamWriter(new FileOutputStream(output),
				charset);

		// TODO: add to contents.
		String file = "/com/globo/opensocial/tests_template.htm";
		InputStream u = this.getClass().getResourceAsStream(file);
		BufferedReader d = new BufferedReader(new InputStreamReader(u,
				getChaset()));
		StringBuffer buffer = new StringBuffer();
		String line;
		while ((line = d.readLine()) != null) {
			buffer.append(line);
			buffer.append("\n");
		}
		String contents = buffer.toString();

		JavaScript tests = new JavaScript(this, getAbsolutePath("tests.js"));

		// Add <head> code
		Writer head = new StringWriter();
		writeCssInclude(head);
		tests.writeCode(head);
		writeDefaultJavaScript(head);
		writeTemplates(head);

		head.flush();

		contents = contents.replace("#HEAD#", head.toString());

		// Add select fields.
		StringBuffer select = new StringBuffer();
		for (File template : getTemplates()) {
			String name = Util.getMethodName(template.getName(), "get");
			select.append("<option>" + name + "</option>");
		}

		contents = contents.replace("#SELECT#", select.toString());

		out.append(contents);
		out.flush();
	}

	/**
	 * Write application
	 * 
	 * @param st
	 * @throws IOException
	 */
	public void write(Writer os) throws IOException {
		writeHeader(os);
		writeContent(os);
		writeFooter(os);
	}

	/**
	 * @throws IOException
	 * 
	 */
	private void writeContent(Writer os) throws IOException {
		os.append("<Content type=\"html\"><![CDATA[\n");
		System.out.println("-------------------------------");
		System.out.println("[INFO] Writing CSS");
		writeCss(os);
		os.append("\n");
		System.out.println("-------------------------------");
		System.out.println("[INFO] Writing JavaScripts");
		writeJavaScripts(os);
		os.append("\n");
		System.out.println("-------------------------------");
		System.out.println("[INFO] Writing templates");
		writeTemplates(os);
		os.append("<div id=\"app__MODULE_ID__\"></div>\n");
		os.append("]]></Content>\n");
	}

	/**
	 * Write all js in the same script tag.
	 * 
	 * @param out
	 * @throws IOException
	 */
	private void writeJavaScripts(Writer out) throws IOException {

		JavaScript.writeOpenScript(out);

		System.out.println("-------------------------------");
		System.out.println("[INFO] Writing Main Code");
		code.writeJs(out);

		out.append("\n");
		System.out.println("-------------------------------");
		System.out.println("[INFO] Writing Extra JS files");

		for (String jsFile : getJavaScripts()) {
			new JavaScript(this, getAbsolutePath(jsFile)).writeJs(out);
		}

		out.append("\n");

		// writeDefaultJavaScript(out);

		String file = "/com/globo/opensocial/default.js";
		InputStream inputStream = this.getClass().getResourceAsStream(file);

		new JavaScript(this, inputStream).writeJs(out);

		JavaScript.writeCloseScript(out);

	}

	private void writeDefaultJavaScript(Writer out) throws IOException {

		System.out.println("-------------------------------");
		System.out.println("[INFO] Writing Embedded Resource default.js");

		String file = "/com/globo/opensocial/default.js";
		InputStream inputStream = this.getClass().getResourceAsStream(file);

		new JavaScript(this, inputStream).writeCode(out);
	}

	private void writeCssInclude(Writer out) throws IOException {
		Css.writeOpenTag(out);
		if (hasCss()) {
			for (String cssFile : getCss()) {
				out.append("@import \"resources/"
						+ getAbsolutePath(cssFile).getName() + "\";\n");
			}
		} else {
			final String defaultCss = "resources/default.css";
			// logger.debug(getAbsolutePath(defaultCss));
			if (getAbsolutePath(defaultCss).isFile()) {
				out.append("@import \"resources/"
						+ getAbsolutePath(defaultCss).getName() + "\";\n");
			}
		}
		Css.writeCloseTag(out);
	}

	/**
	 * Write the CSS Code.
	 * 
	 * @param os
	 * @throws IOException
	 */
	private void writeCss(Writer out) throws IOException {
		// Ok. Now write the code!.
		Css.writeOpenTag(out);
		if (hasCss()) {
			for (String cssFile : getCss()) {
				new Css(this, getAbsolutePath(cssFile)).writeCode(out);
			}
		} else {
			final String defaultCss = "resources/default.css";
			// logger.debug(getAbsolutePath(defaultCss));
			if (getAbsolutePath(defaultCss).isFile()) {
				new Css(this, getAbsolutePath(defaultCss)).writeCode(out);
			}
		}
		Css.writeCloseTag(out);
	}

	/**
	 * | Write the Module Footer.
	 * 
	 * @param os
	 * @throws IOException
	 */
	private void writeFooter(Writer os) throws IOException {
		os.append("</Module>\n");
	}

	/**
	 * 
	 * @param os
	 * @throws IOException
	 */
	public void writeHeader(Writer os) throws IOException {

		System.out.println("[INFO] Writing header");

		StringBuffer buffer = new StringBuffer();

		buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
		buffer.append("<Module>\n");
		buffer.append("<ModulePrefs ");

		buffer.append("title=\"" + getTitle() + "\" ");
		buffer.append("height=\"" + getHeight() + "\" ");
		buffer.append("description=\"" + getDescription() + "\" ");
		if (isSingleton()) {
			buffer.append("singleton=\"true\" ");
		}

		if (getThumbnail() == null) {
			buffer.append("thumbnail=\"" + getHost()
					+ "resources/thumbnail.jpg\" ");
		} else {
			buffer.append("thumbnail=\"" + getHost() + getThumbnail() + "\" ");
		}
		buffer.append(">\n");

		// Required modules.
		for (String r : getRequire()) {
			buffer.append("<Require feature=\"" + r + "\"/>\n");
		}

		buffer.append("</ModulePrefs>\n");

		// add userpref.xml (<UserPref>)
		File config = getAbsolutePath("/userpref.xml");
		if (config.isFile()) {
			buffer.append("\n");
			buffer.append(new Content(this, config).getContent());
			buffer.append("\n");
		}
		os.append(buffer.toString());
	}

	private List<File> getTemplates() {
		ArrayList<File> list = new ArrayList<File>();

		for (String templateDir : this.templates) {

			File templates = getAbsolutePath(templateDir);
			if (!templates.isDirectory()) {
				continue;
			}

			File[] files = templates.listFiles();

			// sort files
			java.util.Arrays.sort(files, new Comparator<File>() {
				public int compare(File o1, File o2) {
					return o1.getName().compareTo(o2.getName());
				}
			});

			for (File file : files) {
				if (!file.isFile() || file.getName().startsWith(".")) {
					continue;
				}
				list.add(file);
			}
		}
		return list;
	}

	private void writeTemplates(Writer out) throws IOException {
		JavaScript.writeOpenScript(out);
		for (File file : getTemplates()) {
			Template t = new Template(this, file.getAbsoluteFile());
			t.writeCode(out);
		}
		JavaScript.writeCloseScript(out);
	}
}
